// Lightweight API client for Next.js (universal) with typed endpoints - JWT via Authorization header only (no cookies)

export type ApiError = {
  status: number;
  message: string;
  details?: any;
};

export type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; error: ApiError };

const DEFAULT_BASE_URL =
  process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:5215';

// Token storage key for client-only persistence (localStorage)
export const TOKEN_STORAGE_KEY = 'hd_token';

function isAbsoluteUrl(u: string) {
  return /^https?:\/\//i.test(u);
}

// Optional global token provider, e.g. () => localStorage.getItem(TOKEN_STORAGE_KEY)
let tokenProvider:
  | (() => string | undefined | Promise<string | undefined>)
  | undefined;

export function setTokenProvider(
  fn: () => string | undefined | Promise<string | undefined>
) {
  tokenProvider = fn;
}

export type ApiInit = Omit<RequestInit, 'headers'> & {
  headers?: Record<string, string>;
  baseUrl?: string;
  token?: string;
  parseJson?: boolean;
};

async function resolveToken(init: ApiInit): Promise<string | undefined> {
  if (init.token != null) return init.token;
  const maybe = tokenProvider?.();
  return maybe instanceof Promise ? await maybe : maybe;
}

export async function apiFetch<T = unknown>(
  path: string,
  init: ApiInit = {}
): Promise<ApiResponse<T>> {
  const baseUrl = init.baseUrl ?? DEFAULT_BASE_URL;
  const url = isAbsoluteUrl(path)
    ? path
    : baseUrl.replace(/\/$/, '') + '/' + path.replace(/^\//, '');

  // Start with Accept header; let callers decide Content-Type (important for FormData)
  const headers: Record<string, string> = {
    Accept: 'application/json',
    ...(init.headers || {}),
  };

  const method = (init.method || 'GET').toUpperCase();

  const token = await resolveToken(init);
  if (token) headers['Authorization'] = 'Bearer ' + token;

  try {
    const res = await fetch(url, { ...init, method, headers });
    const contentType = res.headers.get('content-type') || '';

    if (!res.ok) {
      let errBody: any = undefined;
      try {
        if (contentType.includes('application/json')) {
          errBody = await res.json();
        } else {
          errBody = await res.text();
        }
      } catch {}

      return {
        ok: false,
        error: {
          status: res.status,
          message:
            (errBody && (errBody.error || errBody.message)) || res.statusText,
          details: errBody,
        },
      };
    }

    if (res.status === 204) {
      return { ok: true, data: undefined as unknown as T };
    }

    if (init.parseJson === false) {
      const text = await res.text();
      return { ok: true, data: text as unknown as T };
    }

    if (contentType.includes('application/json')) {
      const data = await res.json();
      return { ok: true, data };
    }

    const text = await res.text();
    try {
      const data = JSON.parse(text);
      return { ok: true, data };
    } catch {
      return { ok: true, data: text as unknown as T };
    }
  } catch (e: any) {
    return {
      ok: false,
      error: { status: -1, message: e?.message || 'NetworkError', details: e },
    };
  }
}

function mergedHeaders(
  a?: Record<string, string>,
  b?: Record<string, string>
): Record<string, string> {
  return { ...(a ?? {}), ...(b ?? {}) };
}

function isFormData(v: any): v is FormData {
  return typeof FormData !== 'undefined' && v instanceof FormData;
}

export const api = {
  get: <T>(path: string, init?: ApiInit) =>
    apiFetch<T>(path, { ...init, method: 'GET' }),

  post: <T>(path: string, body?: any, init?: ApiInit) => {
    const isForm = isFormData(body);
    const prepared =
      body && !isForm ? (typeof body === 'string' ? body : JSON.stringify(body)) : body;
    const ctHeaders: Record<string, string> | undefined = isForm
      ? undefined
      : { 'Content-Type': 'application/json' };
    return apiFetch<T>(path, {
      ...init,
      method: 'POST',
      headers: mergedHeaders(ctHeaders, init?.headers),
      body: prepared as any,
    });
  },

  put: <T>(path: string, body?: any, init?: ApiInit) => {
    const isForm = isFormData(body);
    const prepared =
      body && !isForm ? (typeof body === 'string' ? body : JSON.stringify(body)) : body;
    const ctHeaders: Record<string, string> | undefined = isForm
      ? undefined
      : { 'Content-Type': 'application/json' };
    return apiFetch<T>(path, {
      ...init,
      method: 'PUT',
      headers: mergedHeaders(ctHeaders, init?.headers),
      body: prepared as any,
    });
  },

  patch: <T>(path: string, body?: any, init?: ApiInit) => {
    const isForm = isFormData(body);
    const prepared =
      body && !isForm ? (typeof body === 'string' ? body : JSON.stringify(body)) : body;
    const ctHeaders: Record<string, string> | undefined = isForm
      ? undefined
      : { 'Content-Type': 'application/json' };
    return apiFetch<T>(path, {
      ...init,
      method: 'PATCH',
      headers: mergedHeaders(ctHeaders, init?.headers),
      body: prepared as any,
    });
  },

  delete: <T>(path: string, init?: ApiInit) =>
    apiFetch<T>(path, { ...init, method: 'DELETE' }),

  // Upload helper for multipart/form-data (do not set Content-Type)
  upload: <T>(path: string, form: FormData, init?: ApiInit) =>
    apiFetch<T>(path, {
      ...init,
      method: init?.method ?? 'POST',
      headers: init?.headers,
      body: form,
    }),

  with: (opts: {
    baseUrl?: string;
    token?: string;
    headers?: Record<string, string>;
  }) => ({
    get: <T>(p: string, init?: ApiInit) =>
      api.get<T>(p, { ...init, ...opts, headers: mergedHeaders(opts.headers, init?.headers) }),
    post: <T>(p: string, b?: any, init?: ApiInit) =>
      api.post<T>(p, b, { ...init, ...opts, headers: mergedHeaders(opts.headers, init?.headers) }),
    put: <T>(p: string, b?: any, init?: ApiInit) =>
      api.put<T>(p, b, { ...init, ...opts, headers: mergedHeaders(opts.headers, init?.headers) }),
    patch: <T>(p: string, b?: any, init?: ApiInit) =>
      api.patch<T>(p, b, { ...init, ...opts, headers: mergedHeaders(opts.headers, init?.headers) }),
    delete: <T>(p: string, init?: ApiInit) =>
      api.delete<T>(p, { ...init, ...opts, headers: mergedHeaders(opts.headers, init?.headers) }),
    upload: <T>(p: string, f: FormData, init?: ApiInit) =>
      api.upload<T>(p, f, { ...init, ...opts, headers: mergedHeaders(opts.headers, init?.headers) }),
  }),
};

// ----------------- Auth API (JWT) -----------------
export interface CaptchaResponse {
  captchaId: string;
  imageBase64: string;
  expireSeconds: number;
}

export interface LoginResponse {
  token: string;
  expiresIn: number;
  user: {
    id: string;
    userName: string;
    email: string;
    role: string;
    avatar: string;
    defaultRoute: string;
  };
}

export interface MeResponse {
  id: string;
  userName: string;
  email: string;
  role: string;
  avatar: string;
  defaultRoute: string;
}

export interface OAuthProviders {
  github: boolean;
  gitee: boolean;
}
export interface OAuthStartResponse {
  url: string;
  state: string;
}

export const AuthApi = {
  captcha: () => api.get<CaptchaResponse>('/api/auth/captcha'),
  login: (payload: {
    login: string;
    password: string | null;
    captchaId: string;
    captchaCode: string;
  }) => api.post<LoginResponse>('/api/auth/login', payload),
  me: (token?: string) => api.get<MeResponse>('/api/auth/me', { token }),

  oauthProviders: () =>
    api.get<OAuthProviders>('/api/auth/oauth/providers'),

  githubStart: () =>
    api.get<OAuthStartResponse>('/api/auth/oauth/github/start'),
  githubExchange: (payload: { code: string; redirectUri: string }) =>
    api.post<LoginResponse>('/api/auth/oauth/github/exchange', payload),

  giteeStart: () =>
    api.get<OAuthStartResponse>('/api/auth/oauth/gitee/start'),
  giteeExchange: (payload: { code: string; redirectUri: string }) =>
    api.post<LoginResponse>('/api/auth/oauth/gitee/exchange', payload),
};

// ----------------- Docs API -----------------
export interface CreateProjectRequest {
  name: string;
  description?: string | null;
  avatar?: string | null;
  public: boolean;
}
export interface ProjectResponse {
  id: string;
  name: string;
  description: string;
  avatar: string;
  public: boolean;
}

export interface CreateTreeRequest {
  projectId: string; // Guid
  name: string;
  description?: string | null;
  initialContent?: string | null;
}
export interface TreeResponse {
  id: string;
  name: string;
  description: string;
  projectId: string; // Guid
}

export interface ItemResponse {
  treeId: string;
  content: string;
  lastModified: string | null;
  projectId: string; // Guid
}
export interface UpdateItemRequest {
  content?: string | null;
}

export const DocsApi = {
  createProject: (payload: CreateProjectRequest, token?: string) =>
    api.post<ProjectResponse>('/api/docs/projects', payload, { token }),

  listProjects: (token?: string) =>
    api.get<ProjectResponse[]>('/api/docs/projects', { token }),

  createTree: (payload: CreateTreeRequest, token?: string) =>
    api.post<TreeResponse>('/api/docs/trees', payload, { token }),

  listTrees: (projectId: string, token?: string) =>
    api.get<TreeResponse[]>(`/api/docs/trees?projectId=${encodeURIComponent(projectId)}`, {
      token,
    }),

  getItem: (treeId: string, token?: string) =>
    api.get<ItemResponse>(`/api/docs/items/${encodeURIComponent(treeId)}`, {
      token,
    }),

  updateItem: (treeId: string, payload: UpdateItemRequest, token?: string) =>
    api.put<ItemResponse>(`/api/docs/items/${encodeURIComponent(treeId)}`, payload, {
      token,
    }),
};

// ----------------- Users API -----------------
export interface ProfileRequest {
  userName?: string | null;
  email?: string | null;
}
export interface ProfileResponse {
  id: string;
  userName: string;
  email: string;
  role: string;
  avatar: string;
  defaultRoute: string;
}
export interface AvatarResponse {
  avatar: string;
}

export const UsersApi = {
  updateProfile: (payload: ProfileRequest, token?: string) =>
    api.put<ProfileResponse>('/api/users/profile', payload, { token }),

  updateAvatar: (file: File, token?: string) => {
    const form = new FormData();
    form.append('file', file);
    return api.upload<AvatarResponse>('/api/users/avatar', form, { token, method: 'PUT' });
  },
};

// ----- Client-side token helpers (localStorage) -----
export function setTokenLocal(token: string) {
  if (typeof window === 'undefined') return;
  try {
    window.localStorage.setItem(TOKEN_STORAGE_KEY, token);
  } catch {}
}

export function getTokenLocal(): string | undefined {
  if (typeof window === 'undefined') return undefined;
  try {
    const v = window.localStorage.getItem(TOKEN_STORAGE_KEY);
    return v ?? undefined;
  } catch {
    return undefined;
  }
}

export function clearTokenLocal() {
  if (typeof window === 'undefined') return;
  try {
    window.localStorage.removeItem(TOKEN_STORAGE_KEY);
  } catch {}
}

// Example setup (client):
// setTokenProvider(() => getTokenLocal());
// const res = await AuthApi.me();